#include "../common/common.h"

ClientInfo clients[MAX_CLIENTS];//clients一个数组，系统可以同时管理的最大客户端数量
int client_count = 0;//记录当前已连接的客户端数量
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;//PTHREAD_MUTEX_INITIALIZER 是一个宏，用于初始化互斥锁

// 打印传输进度
void print_progress(long current, long total) {//current：表示当前已完成的进度,total：表示总进度
    int percent = (int)((double)current / total * 100);//计算当前进度占总进度的百分比
    printf("\rProgress: %d%% [", percent);//打印进度信息(Progress: %d%%：打印进度百分比，%d 是整数占位符，%% 表示打印一个 % 符号,[：进度条的开始标记)
    for (int i = 0; i < 50; i++) {//打印一个长度为 50 的进度条
        if (i < percent / 2) {//因为进度条长度为 50，而百分比范围是 0 到 100，所以用 percent / 2 来计算进度条中已填充的长度
            printf("=");//打印填充部分（已完成的部分）
        } else {
             printf(" ");//打印未填充部分（未完成的部分）
        }
    }
    printf("] %ld/%ld bytes", current, total);//打印总进度信息
    fflush(stdout);//调用 fflush(stdout),确保所有输出立即刷新到控制台，而不是等待缓冲区满或程序结束
    if (current >= total) {
        printf("\n");
    }//完成进度时换行,表示进度完成
}

// 处理文件上传
void handle_upload(int sockfd, const char *filename) {
    receive_file(sockfd, filename);//处理客户端上传文件的请求，调用 receive_file 函数接收文件
}

// 处理文件下载
void handle_download(int sockfd, const char *filename) {
    send_file(sockfd, filename);//处理客户端下载文件的请求，调用 send_file 函数发送文件
}

// 列出服务器文件
void handle_list(int sockfd) {
    list_files(sockfd);//处理客户端列出服务器文件的请求，调用 list_files 函数
}

// 处理客户端请求
void handle_client(void *arg) {//主函数，用于处理客户端的连接和请求
    int sockfd = *((int *)arg);//将传入的 void * 类型的参数转换为 int * 类型，并解引用获取套接字文件描述符
    free(arg);//释放传入的指针，因为它是动态分配的（通常在创建线程时分配）

    // 获取客户端IP
    struct sockaddr_in addr;//定义一个结构体变量，用于存储客户端的网络地址信息
    socklen_t addr_len = sizeof(addr);
    getpeername(sockfd, (struct sockaddr *)&addr, &addr_len);//获取与套接字 sockfd 关联的客户端的网络地址信息，并存储到 addr 中
    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &addr.sin_addr, client_ip, INET_ADDRSTRLEN);//将客户端的IP地址从网络字节序转换为可读的字符串格式，并存储到 client_ip 中

    printf("Client connected: %s\n", client_ip);//打印客户端的IP地址，表示客户端已成功连接到服务器

    // 添加到客户端列表
    pthread_mutex_lock(&clients_mutex);//在操作客户端列表之前，先获取互斥锁，以确保对客户端列表的访问是线程安全的
    int client_id = -1;//定义一个变量，用于记录新客户端在 clients 数组中的位置。初始值为 -1，表示未找到空闲槽位
    for (int i = 0; i < MAX_CLIENTS; i++) {//遍历 clients 数组，查找第一个 active 为 0 的槽位（即空闲的客户端位置）
        if (!clients[i].active) {//如果当前槽位的 active 为 0，表示该槽位是空闲的
            clients[i].sockfd = sockfd;//将新客户端的套接字文件描述符存储到该槽位
            clients[i].addr = addr;//将新客户端的网络地址信息存储到该槽位
            clients[i].connect_time = time(NULL);//记录新客户端的连接时间
            clients[i].active = 1;//将该槽位的 active 标记为 1，表示该客户端已激活
            client_id = i;//记录新客户端的位置
            client_count++;//增加当前已连接的客户端数量
            break;//找到空闲槽位后，退出循环
        }
    }
    pthread_mutex_unlock(&clients_mutex);//在完成对客户端列表的操作后，释放互斥锁，允许其他线程访问客户端列表

    //检查是否成功添加
    if (client_id == -1) {//如果 client_id 仍为 -1，说明没有找到空闲槽位，即客户端数量已达到最大限制(MAX_CLIENTS)
        printf("Too many clients, connection refused\n");//拒绝连接
        close(sockfd);//关闭新客户端的套接字文件描述符，释放资源
        return;
    }

    // 处理客户端请求
    while (1) {
        TransferData data;//定义一个 TransferData 类型的变量 data，用于存储从客户端接收的数据
        ssize_t bytes_received = recv(sockfd, &data, sizeof(data), 0);//从客户端的套接字 sockfd 接收数据，存储到 data 中。sizeof(data) 指定接收数据的大小

        //检查连接是否断开
        if (bytes_received <= 0) {//如果 recv 返回值小于等于 0，表示客户端已断开连接或发生错误
            printf("Client disconnected: %s\n", client_ip);//打印客户端断开连接的信息
            break;
        }

        data.client_id = client_id;//将当前客户端的ID设置到 data 结构体中

        switch (data.cmd) {//根据客户端发送的命令类型 data.cmd，执行相应的操作
            case CMD_UPLOAD://调用 handle_upload 函数，处理文件上传请求
                printf("Receiving file: %s\n", data.filename);
                handle_upload(sockfd, data.filename);
                break;
            case CMD_DOWNLOAD://调用 handle_download 函数，处理文件下载请求
                printf("Sending file: %s\n", data.filename);
                handle_download(sockfd, data.filename);
                break;
            case CMD_LIST://调用 handle_list 函数，列出服务器上的文件
                printf("Listing files\n");
                handle_list(sockfd);
                break;
            case CMD_CONNECTIONS://调用 show_connections 函数，显示当前所有客户端的连接信息
                printf("Showing connections\n");
                show_connections(sockfd, clients, &client_count);
                break;
            case CMD_EXIT://客户端请求断开连接
                printf("Client disconnected: %s\n", client_ip);
                break;
            default://如果命令类型未知，打印一条错误信息
                printf("Unknown command\n");
                break;
        }
        //检查是否退出
        if (data.cmd == CMD_EXIT) {
            break;
        }
    }

    // 从客户端列表中移除
    pthread_mutex_lock(&clients_mutex);//获取互斥锁，确保对客户端列表的访问是线程安全的
    clients[client_id].active = 0;//将当前客户端的 active 标记为 0，表示该客户端已断开连接
    client_count--;//减少当前已连接的客户端数量
    pthread_mutex_unlock(&clients_mutex);//释放互斥锁

    close(sockfd);//关闭当前客户端的套接字文件描述符，释放资源
}

// 发送文件
void send_file(int sockfd, const char *filename) {
    FILE *file = fopen(filename, "rb");//filename要发送的文件名
    if (file == NULL) {//如果文件不存在或无法打开，fopen 会返回 NULL
        perror("File not found");
        long filesize = -1; //向客户端发送一个负值的文件大小（-1），表示文件不存在
        send(sockfd, &filesize, sizeof(filesize), 0);//使用 send 函数发送数据，sizeof(filesize) 指定发送的数据大小
        return;//如果文件不存在，直接返回，结束函数执行
    }

    // 获取文件大小
    fseek(file, 0, SEEK_END);//将文件指针移动到文件末尾，用于获取文件大小
    long filesize = ftell(file);//使用 ftell 获取当前文件指针的位置，即文件的大小
    fseek(file, 0, SEEK_SET);//将文件指针重新移动到文件开头，准备读取文件内容
    send(sockfd, &filesize, sizeof(filesize), 0);//将文件大小发送给客户端

    // 发送文件内容
    char buffer[BUFFER_SIZE];//定义一个缓冲区，用于存储每次读取的文件内容
    long total_sent = 0;//定义一个变量，用于记录已发送的总字节数
    size_t bytes_read;//定义一个变量，用于存储每次从文件中读取的字节数

    while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, file)) != 0) {//使用 fread 从文件中读取数据到缓冲区，fread 返回实际读取的字节数，如果返回值为 0，表示文件已读完
        send(sockfd, buffer, bytes_read, 0);//将缓冲区中的数据发送给客户端
        total_sent += bytes_read;//更新已发送的总字节数
        print_progress(total_sent, filesize);//调用 print_progress 函数显示发送进度
    }

    fclose(file);//关闭文件，释放资源
}

// 接收文件
void receive_file(int sockfd, const char *filename) {//filename接收的文件名
    long filesize;//从客户端接收文件大小
    recv(sockfd, &filesize, sizeof(filesize), 0);//使用 recv 函数读取数据，sizeof(filesize) 指定接收的数据大小

    if (filesize == -1) {//如果文件大小为 -1，表示客户端未找到文件
        printf("File not found on client\n");
        return;
    }

    FILE *file = fopen(filename, "wb");//打开文件以二进制模式写入（"wb"）
    if (file == NULL) {
        perror("Failed to create file");
        return;
    }

    char buffer[BUFFER_SIZE];//定义一个缓冲区，用于存储每次接收的文件内容
    long total_received = 0;//定义一个变量，用于记录已接收的总字节数
    ssize_t bytes_received;//定义一个变量，用于存储每次从客户端接收的字节数

    while (total_received < filesize) {//循环接收文件内容，直到接收到的字节数等于文件大小
        bytes_received = recv(sockfd, buffer, BUFFER_SIZE, 0);//从客户端接收数据到缓冲区
        if (bytes_received <= 0) {//如果返回值小于等于 0，表示连接已断开或发生错误
            break;
        }
        fwrite(buffer, 1, bytes_received, file);//将接收到的数据写入文件
        total_received += bytes_received;//更新已接收的总字节数
        print_progress(total_received, filesize);//调用 print_progress 函数显示接收进度
    }

    fclose(file);//关闭文件，释放资源
}

// 列出文件
void list_files(int sockfd) {
    DIR *dir;//定义一个 DIR 类型的指针，用于打开目录
    struct dirent *entry;//定义一个 struct dirent 类型的指针，用于读取目录中的条目
    struct stat file_stat;//定义一个 struct stat 类型的变量，用于获取文件状态信息
    char file_list[BUFFER_SIZE] = {0};//定义一个缓冲区，用于存储文件列表字符串

    dir = opendir(".");//打开当前目录（.）
    if (dir == NULL) {
        perror("Failed to open directory");//如果目录无法打开，使用 perror 打印错误信息
        strcpy(file_list, "Failed to list files");//将错误信息存储到 file_list 中
        send(sockfd, file_list, strlen(file_list) + 1, 0);//将错误信息发送给客户端，发送文件列表，包括终止符(strlen(file_list) + 1)
        return;
    }

    while ((entry = readdir(dir)) != NULL) {//使用 readdir 读取目录中的条目
        if (stat(entry->d_name, &file_stat) == -1) {//使用 stat 获取文件状态信息
            continue;//如果失败，跳过当前条目
        }
        if (S_ISREG(file_stat.st_mode)) {//检查是否为普通文件（非目录）
            strcat(file_list, entry->d_name);//将文件名追加到 file_list 中
            strcat(file_list, "\t");//并添加一个制表符作为分隔符

            //将文件大小格式化为字符串，并追加到 file_list 中
            char size_str[20];
            sprintf(size_str, "%ld bytes\n", file_stat.st_size);
            strcat(file_list, size_str);
        }
    }
    closedir(dir);//关闭目录    

    if (strlen(file_list) == 0) {
        strcpy(file_list, "No files found\n");//如果 file_list 为空，表示目录中没有文件
    }

    send(sockfd, file_list, strlen(file_list) + 1, 0);//将文件列表发送给客户端
}

// 显示连接信息
void show_connections(int sockfd, ClientInfo *clients, int *client_count) {
    char conn_info[BUFFER_SIZE] = {0};//定义一个字符数组 conn_info，用于存储所有客户端的连接信息,使用 {0} 初始化数组，确保内容为空
    char time_str[50];//定义一个字符数组 time_str，用于存储时间格式化后的字符串
    time_t now = time(NULL);//获取当前时间，存储到 now 中

    pthread_mutex_lock(&clients_mutex);//获取互斥锁 clients_mutex，确保对 clients 数组和 client_count 的访问是线程安全的
    sprintf(conn_info, "Total connections: %d\n", *client_count);//使用 sprintf 将当前连接的客户端总数格式化为字符串，并存储到 conn_info 中
    
    for (int i = 0; i < MAX_CLIENTS; i++) {//遍历 clients 数组，查找所有活跃的客户端
        if (clients[i].active) {//检查当前客户端是否处于活跃状态（active 为 1）
            char client_ip[INET_ADDRSTRLEN];//定义一个字符数组 client_ip，用于存储客户端的IP地址
            inet_ntop(AF_INET, &clients[i].addr.sin_addr, client_ip, INET_ADDRSTRLEN);//使用 inet_ntop 将客户端的网络地址（sin_addr）转换为可读的字符串格式
            //AF_INET 表示使用IPv4地址
            //INET_ADDRSTRLEN 是IP地址字符串的最大长度
            double duration = difftime(now, clients[i].connect_time);//使用 difftime 计算当前时间与客户端连接时间的差值（以秒为单位）
            sprintf(time_str, "%.0f seconds", duration);//使用 sprintf 将时间差格式化为字符串，并存储到 time_str 中
            
            char client_entry[100];//定义一个字符数组 client_entry，用于存储单个客户端的连接信息
            sprintf(client_entry, "Client %d: %s, connected for %s\n", 
                    i, client_ip, time_str);//使用 sprintf 将客户端ID、IP地址和连接时长格式化为字符串
            strcat(conn_info, client_entry);//用 strcat 将单个客户端的连接信息追加到 conn_info 中
        }
    }
    pthread_mutex_unlock(&clients_mutex);//释放互斥锁，允许其他线程访问 clients 数组和 client_count

    send(sockfd, conn_info, strlen(conn_info) + 1, 0);//将所有客户端的连接信息发送给客户端
}//strlen(conn_info) + 1 确保发送的字符串包含终止符 \0

// 服务器主函数
void run_server(int port) {//port：服务器监听的端口号
    int server_fd, client_fd;//定义两个整型变量，分别用于存储服务器套接字和客户端套接字
    struct sockaddr_in address;//定义一个 struct sockaddr_in 类型的变量，用于存储服务器和客户端的网络地址信息
    int opt = 1;//定义一个整型变量 opt，用于设置套接字选项
    socklen_t addrlen = sizeof(address);//定义一个变量 addrlen，用于存储 address 的大小

    // 创建线程池
    ThreadPool *pool = thread_pool_create(THREAD_POOL_SIZE, MAX_CLIENTS);//创建一个线程池，用于管理客户端连接
                                          //THREAD_POOL_SIZE 是线程池的最大线程数
                                          //MAX_CLIENTS 是客户端的最大数量
    if (pool == NULL) {
        printf("Failed to create thread pool\n");//如果线程池创建失败，打印错误信息并返回
        return;
    }

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {//AF_INET 表示使用IPv4协议，SOCK_STREAM 表示使用TCP协议
        perror("socket failed");
        exit(EXIT_FAILURE);//如果创建失败，打印错误信息并退出程序
    }

    // 设置套接字选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {//置套接字选项，允许地址和端口的重用
                                //SO_REUSEADDR 和 SO_REUSEPORT 是套接字选项，用于避免端口冲突
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    //配置服务器的网络地址信息
    address.sin_family = AF_INET;//AF_INET 表示使用IPv4协议
    address.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY 表示绑定到所有可用的网络接口
    address.sin_port = htons(port);//htons(port) 将端口号转换为网络字节序

    // 绑定套接字
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_fd, MAX_CLIENTS) < 0) {//MAX_CLIENTS 是最大连接数，等待客户端连接
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server started on port %d\n", port);//打印服务器启动信息

    // 接受客户端连接
    while (1) {
        if ((client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0) {
            perror("accept");
            continue;
        }

        // 为每个客户端创建任务
        int *new_sock = malloc(sizeof(int));//为客户端套接字分配动态内存
        *new_sock = client_fd;//将客户端套接字存储到动态分配的内存中
        thread_pool_add_task(pool, handle_client, (void *)new_sock);//将客户端连接的任务添加到线程池中
    }

    // 清理
    thread_pool_destroy(pool);//清理线程池资源
    close(server_fd);//关闭服务器套接字
}

int main() {
    printf("Starting server on port 8080...\n");//程序的入口点
    run_server(8080);//调用 run_server 函数，启动服务器并监听端口 8080
    return 0;
}